/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

#include "simodo/inout/token/Scanner.h"

#include <cassert>


namespace simodo::inout
{

bool Scanner::putCharToBuffer()
{
    if (_is_end_of_file_reached)
        return false;

    // ВАЖНО! Предполагается, что входной поток содержит коды текста длиной не более двух байт
    /// \todo При чтении символов из входного потока потенциально возможно искажение (если размер кода текста > 16 бит).
    /// Кроме того, это приводит к слабости кода перед изменениями, например, при переходе на строки по 32 бита в символе.
    /// Решение: сделать класс с параметрическим типом на размер символа в потоке и строке.
    char16_t ch = _input.get();

    if (ch == std::char_traits<char16_t>::eof())
    {
        _is_end_of_file_reached = true;
        return false;
    }

    _buffer.push_back(ch);
    return true;
}

Scanner::Scanner(uri_index_t uri_index, InputStream_interface &input_stream, context_index_t context_index)
    : _uri_index(uri_index)
    , _input(input_stream)
    , _context_index(context_index)
    , _buffer()
    , _current_line(0)
    , _current_character(0)
    , _is_end_of_file_reached(false)
{
    _buffer.reserve(20);
}

bool Scanner::shift(size_t length)
{
    assert(_buffer.size() >= length);  // Нельзя сдвигать за пределы буфера (откуда токенайзеру знать то, чего он не видел?)

    // Вычисляем локацию
    for(size_t i=0; i < length; ++i)
    {
        char16_t ch = _buffer[i];

        if (ch == u'\n')
        {
            _current_line ++;
            _current_character = 0;
        }
        else
            _current_character ++;
    }

    // Выпиливаем неактуальное
    _buffer = _buffer.substr(length);

    if (_buffer.empty())
        return putCharToBuffer();

    return true;
}

TokenLocation Scanner::makeTokenLocation() const 
{
    return {_uri_index,{_token_start_line,_token_start_character,_current_line,_current_character}};
}

void Scanner::fixLocation(context_index_t context_index)
{
    _token_start_line = _current_line;
    _token_start_character = _current_character;
    _token_context_index = context_index;
}

bool Scanner::startsWith(char16_t ch)
{
    if (_buffer.empty())
    {
        if (_is_end_of_file_reached || _input.eof())
            return false;

        bool ok = putCharToBuffer();
        if (!ok)
            return false; // Достигли конца потока
    }

    return (_buffer[0] == ch);
}

bool Scanner::startsWith(const std::u16string &str)
{
    if (str.empty())
        return false;

    while(_buffer.size() < str.size())
    {
        if (_is_end_of_file_reached || _input.eof())
            return false;

        bool ok = putCharToBuffer();
        if (!ok)
            return false; // Достигли конца потока
    }

    return (_buffer.compare(0,str.size(),str) == 0);
}

bool Scanner::startsWithAnyOf(const std::u16string &str)
{
    if (str.empty())
        return false;

    if (_buffer.empty())
    {
        if (_is_end_of_file_reached || _input.eof())
            return false;

        bool ok = putCharToBuffer();
        if (!ok)
            return false; // Достигли конца потока
    }

    return (str.find(_buffer[0]) != std::u16string::npos);
}

char16_t Scanner::getFirstChar(void)
{
    return getChar();
}

char16_t Scanner::getChar(size_t pos)
{
    while(_buffer.size() < pos+1)
    {
        if (_is_end_of_file_reached || _input.eof())
            return std::char_traits<char16_t>::eof();

        bool ok = putCharToBuffer();
        if (!ok)
            return std::char_traits<char16_t>::eof(); // Достигли конца потока
    }

    return _buffer[pos];
}

}